package com.example.redisspringboot.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Objects;
import java.util.Random;

/**
 * @author lichenyang
 * @create 2021-05-06 10:40 上午
 * <p>
 * 需求:
 * 商品库存: -1 (使用String类型)
 * 秒杀成功者清单: +1 (使用set类型 -- 一个用户只能秒杀一次)
 * <p>
 * 共100000个请求
 * 1000个并发
 * 使用 ~/路径下的 postfile 文件中的内容作为参数
 * 注意了 初始阶段最好不要设置太高的请求和并发,如果设置太高 可能会出现 apr_socket_recv: Connection reset by peer (54) 异常
 * 请求数设置 1k, 并发数设置20即可
 * ab -n 100000 -c 1000 -p ~/postfile -T 'application/x-www-form-urlencoded' http://127.0.0.1:8080/redisTest/execute
 * <p>
 * 可能会出现的问题:
 * 1. 2000个请求, 2000个并发, 有些写成一直等待执行, 导致连接redis超时
 * 解决方案:使用redisPool 设置超时时间
 * <p>
 * 2. 超卖问题
 * 解决方案:使用redis事务
 * <p>
 * 3. 库存多余问题
 * 解决方案:使用lua脚本
 */
@RestController
@RequestMapping("redisTest")
public class RedisController {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @GetMapping("testRedis")
    public String testRedis() {
        // 设置值到redis中
        redisTemplate.opsForValue().set("name", "zhangsan");
        String name = redisTemplate.opsForValue().get("name");
        return name;
    }

    /**
     * 问题之 超卖问题
     *
     * @param productId
     * @return
     */
    @PostMapping("questionCode_overSold")
    public boolean questionCode_overSold(String productId) {
        //1. uid和product的非空判断
        if (Objects.isNull(productId)) {
            return false;
        }
        String userKey = "sucess buy " + productId + " users";
        String uid = new Random().nextInt(50000) + "";
        //2. 获取库存
        if (verify(productId, userKey, uid)) {
            return false;
        }
        redisTemplate.opsForValue().decrement(productId);
        redisTemplate.opsForSet().add(userKey, uid);
        return true;
    }


    /**
     * 解决: 超卖问题
     * 新问题: 商品有多余 由于watch如果执行失败就放弃执行了, 所以会出现
     * 超卖问题演示: 如果还是出现超卖,就只能说明事务是有问题的, 演示的时候 将redis中的库存设置小, 将请求设置大
     * 多余问题演示: 请求数不变 修改库存大小, 库存有多余
     *
     * @param productId
     * @return
     */
    @PostMapping("questionCode_leaveoods")
    public boolean questionCode_leaveGoods(String productId) {
        //1. uid和product的非空判断
        if (Objects.isNull(productId)) {
            return false;
        }
        String userKey = "sucess buy " + productId + " users";
        String uid = new Random().nextInt(50000) + "";
        redisTemplate.watch(productId);
        if (verify(productId, userKey, uid)) {
            return false;
        }
        /*
        这种方式不行
        设置支持事务
        redisTemplate.setEnableTransactionSupport(true);

        redisTemplate.multi();
        //7. 秒杀过程, 吧秒杀成功用户添加到清单中
        redisTemplate.opsForValue().decrement(productId);
        redisTemplate.opsForSet().add(userKey, uid);
        redisTemplate.exec();
        return true;
        */
        Boolean execute = redisTemplate.execute(new SessionCallback<Boolean>() {
            @Override
            public <K, V> Boolean execute(RedisOperations<K, V> operations) throws DataAccessException {
                String userKey = "sucess buy " + productId + " users";
                String uid = new Random().nextInt(50000) + "";
                operations.watch((K) productId);
                if (verify(productId, userKey, uid)) {
                    return false;
                }
                operations.multi();
                //7. 秒杀过程, 吧秒杀成功用户添加到清单中
                operations.opsForValue().decrement((K) productId);
                operations.opsForSet().add((K) userKey, (V) uid);
                operations.exec();
                return true;
            }
        });
        return execute;
    }

    private boolean verify(String productId, String userKey, String uid) {
        //2. 获取库存
        String kcNumStr = redisTemplate.opsForValue().get(productId);
        if (Objects.isNull(kcNumStr)) {
            System.out.println("秒杀还没开始");
            return true;
        }

        //3. 判断如果商品数量 < 1, 秒杀结束
        int kcNum = Integer.parseInt(kcNumStr);
        if (kcNum <= 0) {
            System.out.println("秒杀结束");
            return true;
        }

        //4. 判断用户是否重复秒杀
        if (redisTemplate.opsForSet().isMember(userKey, uid)) {
            System.out.println("已经秒杀成功,不能重复秒杀");
            return true;
        }
        return false;
    }


    /**
     * 初始化redis数据
     */
    @GetMapping("initRedis")
    public void initRedis() {
        redisTemplate.execute((RedisCallback<Object>) connection -> {
            connection.flushDb();
            return "ok";
        });
        // 设置商品库存
        redisTemplate.opsForValue().set("num1", "500");
        System.out.println("初始化完成");
    }
}
